﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Net;
using System.Text.RegularExpressions;
using System.Threading;

namespace Gakita.View.Menu
{
    /* This class keeps track of the highscore lists. It also handles uploading and downloading highscores from the server.
       The username is stored here so that it can be used when uploading. */
    class HighScoreModel : IModelObserver
    {
        private const string _requestScoreURL               =       "http://www.asaigo.com/gakita/requestscores.php";
        private const string _sendScoreURL                  =       "http://www.asaigo.com/gakita/newscore.php";

        private List<HighScore> _firstLevelHighScores       =       new List<HighScore>();
        private List<HighScore> _secondLevelHighScores      =       new List<HighScore>();
        private List<HighScore> _thirdLevelHighScores       =       new List<HighScore>();
        private List<HighScore> _fourthLevelHighScores      =       new List<HighScore>(); 

        private int _currentID                              =       -1;
        private int _currentTime                            =       0;
        private int _currentRank                            =       -1;
        private String _currentUsername;

        private Model.Level.Levels _activeHighScores        =       Model.Level.Levels.first;
        private Model.Level.Levels _currentLevel;

        public List<HighScore> GetScores(string aModeID, string aFormat)
        {
            string postString = "modeid=" + aModeID + "&Format=" + aFormat;
            return parseToHighscoreList(webPost(_requestScoreURL, postString));
        }

        private int GetRankFromID(int aID, string aModeID, string aFormat)
        {
            string postString = "modeid=" + aModeID + "&Format=" + aFormat + "&id=" + aID;
            return int.Parse(webPost(_requestScoreURL, postString));
        }
        
        /* This method opens a new thread that downloads the top 10 highscores for each levels. 
           Called when the player presses the highscores menu option in the main menu */
        public void LoadAllHighScores()
        {
            Thread LoadHighScoresThread = new Thread(new ThreadStart(RetrieveHighScoresFromServer));
            LoadHighScoresThread.Start();
        }

        /* This method puts the highscores list for each level into a list of highscore lists, which is returned */
        public List<List<HighScore>> GetAllHighScores()
        {
            List<List<HighScore>> ret = new List<List<HighScore>>();
            ret.Add(_firstLevelHighScores);
            ret.Add(_secondLevelHighScores);
            ret.Add(_thirdLevelHighScores);
            ret.Add(_fourthLevelHighScores);
            return ret;
        }

        /* This method asks for the top 10 highscores from each level to be downloaded. */
        private void RetrieveHighScoresFromServer()
        {
            _firstLevelHighScores = GetScores("first", "TOP10");
            _secondLevelHighScores = GetScores("second","TOP10");
            _thirdLevelHighScores = GetScores("third","TOP10");
            _fourthLevelHighScores = GetScores("fourth", "TOP10");
        }

        private void ResetCurrent()
        {
            _currentID = -1;
            _currentRank = -1;
        }

        /* Switches with level is the currently active one for highscores */
        public void NextActiveHighScore()
        {
            if ((int)_activeHighScores > 3)
            {
                _activeHighScores = Model.Level.Levels.first;
            }
            else
            {
                _activeHighScores += 1;
            }
        }

        /* Switches with level is the currently active one for highscores */
        public void PreviousActiveHighScore()
        {
            if ((int)_activeHighScores < 2)
            {
                _activeHighScores = Model.Level.Levels.fourth;
            }
            else
            {
                _activeHighScores -= 1;
            }
        }
        /* This method gets information from the model when a level is over and then starts a new thread
           to upload the current highscore to the server */
        public void LevelIsOver(int aTime, Model.Level.Levels aCurrentLevel)
        {
            ResetCurrent();
            _currentLevel = aCurrentLevel;
            _currentTime = aTime;
            Thread sendScoreThread = new Thread(new ThreadStart(SendScoreAndSetIDAndSetRank));
            sendScoreThread.Start();
        }

        /* Uploads a highscore and then sets the currentRank, so it can be displayed between levels */
        private void SendScoreAndSetIDAndSetRank()
        {
            // The php script returns the current id when uploading a highscore
            int id = int.Parse(sendScore(_currentLevel.ToString(), _currentUsername, _currentTime));
            SetID(id);
            _currentRank = GetRankFromID(_currentID,_currentLevel.ToString(),"RANK");
        }

        /* The method sends a post request to the server and gets a string back */
        private string webPost(string aURI, string aPostString)
        {
            const string REQUEST_METHOD_POST = "POST";
            const string CONTENT_TYPE = "application/x-www-form-urlencoded";

            Stream dataStream = null;
            StreamReader reader = null;
            WebResponse response = null;
            String responseString = null;

            // Create a request using a URL that can receive a post.
            WebRequest request = WebRequest.Create(aURI);
            // Set the Method property of the request to POST.
            request.Method = REQUEST_METHOD_POST;
            // Create POST data and convert it to a byte array.
            string postData = aPostString;
            byte[] byteArray = Encoding.UTF8.GetBytes(postData);
            // Set the ContentType property of the WebRequest.
            request.ContentType = CONTENT_TYPE;
            // Set the ContentLength property of the WebRequest.
            request.ContentLength = byteArray.Length;
            // Get the request stream.
            dataStream = request.GetRequestStream();
            // Write the data to the request stream.
            dataStream.Write(byteArray, 0, byteArray.Length);
            // Close the Stream object.
            dataStream.Close();
            // Get the response.
            response = request.GetResponse();
            // Display the status.
            Console.WriteLine(((HttpWebResponse)response).StatusDescription);
            // Get the stream containing content returned by the server.
            dataStream = response.GetResponseStream();
            // Open the stream using a StreamReader for easy access.
            reader = new StreamReader(dataStream);
            // Read the content.
            responseString = reader.ReadToEnd();
            // Display the content.
            Console.WriteLine(responseString);
            // Clean up the streams.
            if (reader != null) reader.Close();
            if (dataStream != null) dataStream.Close();
            if (response != null) response.Close();
        
            return responseString;
        }

        /* This method parses the string returned from the server into highscore objects, which are put in a list */
        private List<HighScore> parseToHighscoreList(string aTableString)
        {
            const string SERVER_VALID_DATA_HEADER = "SERVER_";

            List<HighScore> ret = new List<HighScore>();

            if (aTableString.Trim().Length < SERVER_VALID_DATA_HEADER.Length ||
            !aTableString.Trim().Substring(0, SERVER_VALID_DATA_HEADER.Length).Equals(SERVER_VALID_DATA_HEADER))
            {
                return null;
            }

            string toParse = aTableString.Trim().Substring(SERVER_VALID_DATA_HEADER.Length);

            string[] rows = Regex.Split(toParse, "_ROW_");

            for (int i = 0; i < 10; i++)
            {
                if (rows.Length > i && rows[i].Trim() != "")
                {
                    string[] cols = Regex.Split(rows[i], "_COL_");
                    if (cols.Length == 3)
                    {
                        string name = cols[0].Trim();
                        int time = int.Parse(cols[1].Trim());
                        int rank = int.Parse(cols[2].Trim());

                        ret.Add(new HighScore(rank,name,time));
                    }
                }
            }
            return ret;
        }

        private string hashString(string aValue)
        {
            System.Security.Cryptography.MD5CryptoServiceProvider x = new System.Security.Cryptography.MD5CryptoServiceProvider();
            byte[] data = System.Text.Encoding.ASCII.GetBytes(aValue);
            data = x.ComputeHash(data);
            string ret = "";
            for (int i = 0; i < data.Length; i++)
            {
                ret += data[i].ToString("x2").ToLower();
            }
            return ret;
        }

        /* Method for uploading a highscore to the server. Returns the database id of the uploaded highscore */
        public string sendScore(string aModeID, string aName, int aTime)
        {
            string highscoreString = aName + aTime + "GakitaSuperSecretPasswordString";
            string postString = "modeid=" + aModeID + "&name=" + aName + "&time=" + aTime + "&Hash=" + hashString(highscoreString);
            string response = null;
            response = webPost(_sendScoreURL, postString);
            return response.Trim();
        }

        public int GetRank()
        {
            return _currentRank;
        }

        public int GetTime()
        {
            return _currentTime;
        }

        public Model.Level.Levels GetLevel()
        {
            return _currentLevel;
        }

        public void SetUsername(string aUsername)
        {
            _currentUsername = aUsername;
        }

        public void SetID(int aID)
        {
            _currentID = aID;
        }

        public int GetActiveHighscore()
        {
            return (int)_activeHighScores - 1;
        }

    }
}
